#include "communication.h"
#include "protocol.h"

#include "qextserialport/qextserialport.h"

#include <QDebug>
#include <QTcpSocket>
#include <QJsonObject>
#include <QJsonArray>
#include <QTimerEvent>
#include <QDataStream>


Communication::Communication(QObject *parent)
    : QObject(parent)
    , m_control(nullptr)
    , m_state(UnconnectedState)
    , m_serialNumber(0)
{
    m_timer = new QElapsedTimer;
    qDebug() << "Type: " << QElapsedTimer::isMonotonic();
}

void Communication::timerEvent(QTimerEvent *event)
{
    if (event->timerId() != m_timeId) {
        return;
    }

    QList<WaitReplyItem*> items;
    for(int i=0; i<m_list.size(); ++i) {
        int value = (m_list[i]->m_timer.elapsed() / Protocol::E_TIMEOUT);
        switch (value) {
        case 0:
            continue;
        case 1:
        case 2:
        case 3:
            if (m_list[i]->m_repeatTimes < value) {
                m_control->write(m_list[i]->m_packet);
                m_control->flush();
                m_list[i]->m_repeatTimes++;
                qDebug() << "Timeout Retransmitted: " << m_list[i]->m_packet.toHex();
            }
            break;
        default: {
            switch (m_list[i]->cmd()) {
            case Protocol::E_PACKV1_SHAKEHAND:
                disconnectFromHost();
                break;
            default:
                break;
            }
            emit aboutToProcessed(m_list[i]->cmd(), false);
            items.append(m_list[i]);
            qDebug() << "Timeout: " << m_list[i]->cmd() << ", " << m_list[i]->sn();
            break; }
        }
    }

    for (int i=0; i<items.size(); ++i) {
        m_list.removeOne(items[i]);
        delete items[i];
    }

    if (!items.isEmpty()) {
//        emit errorOccurred(E_COMM_TIMEOUT);
        qWarning("#####error######");
    }
}

void Communication::connectToHost(const QString &portName)
{
    if (m_state == ConnectedState
            || m_state == ConnectingState
            || m_state == ClosingState) {
        qWarning("Communication::connectToHost() called when already closing or connecting/connected to \"%s\"", qPrintable(portName));
        return;
    }

    m_state = ConnectingState;
    emit stateChanged(m_state);

    if (m_timeId == 0) {
        m_timeId = startTimer(1000, Qt::PreciseTimer);
        if (m_timeId == 0) {
            m_state = UnconnectedState;
            emit stateChanged(m_state);
            return;
        }
    }

    if (!m_control) {
        m_control = new QextSerialPort;
    }

    m_control->setPortName(portName);
    m_control->setBaudRate(BAUD115200);
    m_control->setDataBits(DATA_8);
    m_control->setStopBits(STOP_1);
    m_control->setParity(PAR_NONE);
    m_control->setFlowControl(FLOW_OFF);

    if (m_control->open(QIODevice::ReadWrite)) {
        connect(m_control, &QIODevice::readyRead, this, &Communication::onReadReady);
        qDebug() << Q_FUNC_INFO;
        onRequestInteraction();
    } else {
        disconnectFromHost();
    }
}

void Communication::disconnectFromHost()
{
    if (m_state == UnconnectedState) {
        return;
    }

    if (m_timeId != 0) {
        killTimer(m_timeId);
        m_timeId = 0;
    }

    if (m_control) {
        m_state = ClosingState;
        emit stateChanged(m_state);

        m_control->close();
        delete m_control;
        m_control = nullptr;

        m_state = UnconnectedState;
        emit stateChanged(m_state);
        emit disconnected();
    } else {

    }
}

bool Communication::isValid() const
{
    return (m_control && m_control->isOpen() && (m_state == ConnectedState));
}

void Communication::onFuzzyPIDAdjustValue(float value)
{
    if (!isValid()) {
        return;
    }

    QByteArray packet;
    QDataStream out(&packet, QIODevice::WriteOnly);
    out.setByteOrder(QDataStream::LittleEndian);
    out.setVersion(QDataStream::Qt_4_8);
    out << static_cast<quint8>(Protocol::E_PACKV1_HEAD);
    out << quint8(0);
    out << static_cast<quint8>(Protocol::E_PACKV1_TOUCH_SCREEN);
    out << static_cast<quint8>(Protocol::E_PACKV1_DEVICETYPE_HOST);
    out << static_cast<quint8>(Protocol::E_PACKV1_DEVICE_ADDRESS);
    out << quint8(Protocol::E_PACKV1_ADJUSTED_VALUE);
    out << m_serialNumber++;
    out << quint8(Protocol::E_PACKV1_NEED_FEEDBACK);
    out << static_cast<quint32>(value*100);
    auto p = out.device()->pos();
    out.device()->seek(1);
    out << static_cast<quint8>(packet.size()+2);
    out.device()->seek(p);
    out << Protocol::check_sum(reinterpret_cast<const quint8*>(packet.data()), static_cast<quint8>(packet.size()));
    out << static_cast<quint8>(Protocol::E_PACKV1_TAIL);

    m_control->write(packet);
    m_control->flush();
}

//!
//! \brief 握手通讯交互
//!
void Communication::onRequestInteraction()
{
    QByteArray packet;
    QDataStream out(&packet, QIODevice::WriteOnly);
    out.setByteOrder(QDataStream::LittleEndian);
    out.setVersion(QDataStream::Qt_4_8);
    out << static_cast<quint8>(Protocol::E_PACKV1_HEAD);
    out << quint8(0);
    out << static_cast<quint8>(Protocol::E_PACKV1_TOUCH_SCREEN);
    out << static_cast<quint8>(Protocol::E_PACKV1_DEVICETYPE_HOST);
    out << static_cast<quint8>(Protocol::E_PACKV1_DEVICE_ADDRESS);
    out << quint8(Protocol::E_PACKV1_SHAKEHAND);
    out << m_serialNumber++;
    out << quint8(Protocol::E_PACKV1_NEED_FEEDBACK);
    out << quint8(1);
    auto p = out.device()->pos();
    out.device()->seek(1);
    out << static_cast<quint8>(packet.size()+2);
    out.device()->seek(p);
    out << Protocol::check_sum(reinterpret_cast<const quint8*>(packet.data()), static_cast<quint8>(packet.size()));
    out << static_cast<quint8>(Protocol::E_PACKV1_TAIL);

    addWaitReplyItem(packet);
    m_control->write(packet);
    m_control->flush();
}

void Communication::onReadReady()
{
    qDebug() << "--------start----------";
    QByteArray packet;
    while (extract(packet)) {
        qDebug() << "begin parse: " << m_timer->restart() << "ms";
        QDataStream in(&packet, QIODevice::ReadOnly);
        in.setByteOrder(QDataStream::LittleEndian);
        in.setVersion(QDataStream::Qt_4_8);
        in.device()->seek(5);

        quint8 fromCmd;
        in >> fromCmd;

        switch (fromCmd) {
        case Protocol::E_PACKV1_FEEDBACK: {
            onParse1(in);
            break;}
        case Protocol::E_PACKV1_SHAKEHAND: {
            onParse2(in);
            break; }
        case Protocol::E_PACKV1_TEMPERATURE: {
            onParse3(in);
            break; }
        default:
            qDebug("---------//_|_\\----------");
            break;
        }
        qDebug() << "end parse: " << m_timer->restart() << "ms";
    }
}

void Communication::onParse1(QDataStream &in)
{
    quint8 fromSn, fromNeedFeedback;
    in >> fromSn >> fromNeedFeedback;

    quint8 fromReplyCmd, fromReplySn;
    in >> fromReplyCmd >> fromReplySn;

    auto item = std::find_if(std::begin(m_list), std::end(m_list), [fromReplySn](WaitReplyItem *item) {
        if (fromReplySn == item->sn()) {
            return true;
        } else {
            return false;
        }
    });

    if (std::end(m_list) == item) {
        return;
    }

    emit aboutToProcessed((*item)->cmd(), true);

    switch ((*item)->cmd()) {
    case Protocol::E_PACKV1_SHAKEHAND: {
        if (m_state == ConnectingState) {
            m_state = ConnectedState;
            emit stateChanged(m_state);
        }
        break; }
    default:
        break;
    }
    qDeleteAll(item, item+1);
    m_list.erase(item);
}

void Communication::onParse2(QDataStream &in)
{
    quint8 fromSn, fromNeedFeedback;
    in >> fromSn >> fromNeedFeedback;

    if (fromNeedFeedback) {
        sendRegularACK(Protocol::E_PACKV1_SHAKEHAND, fromSn);
    }
}

void Communication::onParse3(QDataStream &in)
{
    quint8 fromSn, fromNeedFeedback;
    in >> fromSn >> fromNeedFeedback;

    if (fromNeedFeedback) {
        sendRegularACK(Protocol::E_PACKV1_TEMPERATURE, fromSn);
    }

    qint32 temperature;
    in >> temperature;

    emit temperatureChanged(temperature * 0.1f);
}

void Communication::addWaitReplyItem(const QByteArray &packet)
{
    WaitReplyItem *item = new WaitReplyItem;
    item->m_packet    = packet;
    item->m_repeatTimes = 0;
    item->m_timer.start();
    m_list.append(item);
}

//!
//! \brief 从循环buffer中提取单个完整数据包
//! \param packet
//! \return
//!
bool Communication::extract(QByteArray &packet)
{
    packet = QByteArray();

    for (;;) {
        QByteArray data = m_control->peek(m_control->bytesAvailable());
        if (data.isEmpty()) {
            return false;
        }

        if (static_cast<quint8>(data[0]) != Protocol::E_PACKV1_HEAD) {
            qDebug() << "Dirty data: " << m_control->read(1).toHex();
            continue;
        }

        if (data.size() < Protocol::E_PACKV1_MIN_LENGTH) {
            return false;
        }

        if (static_cast<quint8>(data[1]) == 0) {
            qDebug() << "Dirty data: " << m_control->read(2).toHex();
            continue;
        }

        if (static_cast<quint8>(data[1]) > data.length()) {
            return false;
        }

        quint8 tail = static_cast<quint8>(data[1]) - 1;
        if (static_cast<quint8>(data[tail]) != Protocol::E_PACKV1_TAIL) {
            qDebug() << "Dirty data: " << m_control->read(2).toHex();
            continue;
        }

        quint8 lcr = static_cast<quint8>(data[1]) - 2;
        quint8 checksum = Protocol::check_sum(reinterpret_cast<const quint8*>(data.data()), static_cast<quint8>(static_cast<quint8>(data[1])-2));
        if (static_cast<quint8>(data[lcr]) != checksum) {
            qDebug() << "Dirty data: " << m_control->read(2).toHex();
            continue;
        }

        packet = m_control->read(static_cast<quint8>(data[1]));
        break;
    };

//    if (static_cast<quint8>(packet[2]) == Protocol::E_PACKV1_DEVICETYPE_HOST
//            || static_cast<quint8>(packet[2]) == Protocol::E_PACKV1_WLC) {
//        return false;
//    }

    //处理重复包
#if 0
    quint8 packetCmd = packet[5];
    quint8 packetId  = packet[6];
    quint8 feedback  = packet[7];
    if ((*qDefaultPacketIdHandle).contains(packetCmd)) {
        if ((*qDefaultPacketIdHandle)[packetCmd]->idCache.isEmpty()) {
            (*qDefaultPacketIdHandle)[packetCmd]->idCache.enqueue(packetId);
        } else {
            if ((*qDefaultPacketIdHandle)[packetCmd]->idCache.indexOf(packetId) == -1) {
                if ((*qDefaultPacketIdHandle)[packetCmd]->idCache.size() >= 60) {
                    (*qDefaultPacketIdHandle)[packetCmd]->idCache.dequeue();
                }
                (*qDefaultPacketIdHandle)[packetCmd]->idCache.enqueue(packetId);
            } else {
                if (feedback) {
                   sendRegularACK(packetCmd, packetId);
                }
                qCritical() << "[command, id] exists: " << packetCmd << packetId;
                return false;
            }
        }
    }
#endif

    qDebug() << "data: " << packet.toHex();

    return true;
}

void Communication::sendRegularACK(quint8 cmd, quint8 sn)
{
//    if (!isValid()) {
//        return;
//    }

    QByteArray packet;
    QDataStream out(&packet, QIODevice::WriteOnly);
    out.setByteOrder(QDataStream::LittleEndian);
    out.setVersion(QDataStream::Qt_4_8);
    out << static_cast<quint8>(Protocol::E_PACKV1_HEAD);
    out << quint8(0);
    out << static_cast<quint8>(Protocol::E_PACKV1_TOUCH_SCREEN);
    out << static_cast<quint8>(Protocol::E_PACKV1_DEVICETYPE_HOST);
    out << static_cast<quint8>(Protocol::E_PACKV1_DEVICE_ADDRESS);
    out << quint8(Protocol::E_PACKV1_FEEDBACK);
    out << m_serialNumber++;
    out << quint8(Protocol::E_PACKV1_NOT_FEEDBACK);
    out << cmd;
    out << sn;
    auto p = out.device()->pos();
    out.device()->seek(1);
    out << static_cast<quint8>(packet.size()+2);
    out.device()->seek(p);
    out << Protocol::check_sum(reinterpret_cast<const quint8*>(packet.data()), static_cast<quint8>(packet.size()));
    out << static_cast<quint8>(Protocol::E_PACKV1_TAIL);

    m_control->write(packet);
    m_control->flush();

    qDebug() << "ack-packet: " << packet.toHex();
}
